package com.gitee.wsl.compose.use.coroutines


import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import com.gitee.wsl.compose.base.getValue
import com.gitee.wsl.compose.base.setValue
import com.gitee.wsl.compose.base.useLatestRef
import com.gitee.wsl.compose.base.useRef
import com.gitee.wsl.compose.use.OnEndCallback
import com.gitee.wsl.compose.use.useCreation
import com.gitee.wsl.compose.use.useGetState
import com.gitee.wsl.compose.use.useState
import com.gitee.wsl.ext.any.isNotNull
import com.gitee.wsl.ext.base.asBoolean
import com.gitee.wsl.ext.base.currentTime
import com.gitee.wsl.reflect.Options
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.datetime.Instant


/*
  Description: A hook for manage countdown.
  Author: Junerver
  Date: 2024/7/8-14:22
  Email: junerver@gmail.com
  Version: v1.0
*/

/**
 * Countdown options
 *
 * @constructor Create empty Countdown options
 * @property leftTime directly set the remaining time of the countdown
 * @property targetDate Lower priority than [leftTime], set a target time
 *     for the end of the countdown
 * @property interval countdown interval
 * @property onEnd callback function for end of countdown
 */
@Stable
data class CountdownOptions internal constructor(
    var leftTime: Duration? = null,
    var targetDate: Instant? = null,
    var interval: Duration = 1.seconds,
    var onEnd: OnEndCallback? = null,
) {
    companion object : Options<CountdownOptions>(::CountdownOptions)
}

@Composable
private fun useCountdown(options: CountdownOptions): CountdownHolder {
    val (leftTime, targetDate, interval, onEnd) = options
    require(leftTime.isNotNull() || targetDate.isNotNull()) {
        "'leftTime' or 'targetDate' must be set"
    }
    val target = useCreation {
        if (leftTime.isNotNull()) {
            currentTime + leftTime!!
        } else {
            targetDate
        }
    }.current

    val (timeLeft, setTimeLeft) = useGetState(calcLeft(target))
    val onEndRef by useLatestRef(onEnd)
    var pauseRef by useRef(default = {})
    val (resume, pause) = useInterval(
        optionsOf = {
            period = interval
        }
    ) {
        val targetLeft = calcLeft(target)
        setTimeLeft(targetLeft)
        if (targetLeft == 0.seconds) {
            pauseRef()
            onEndRef?.invoke()
        }
    }
    pauseRef = pause
    useEffect(interval) {
        if (!target.asBoolean()) {
            setTimeLeft(0.seconds)
            return@useEffect
        }
        setTimeLeft(calcLeft(target))
        resume()
    }
    val formatRes = useState { parseDuration(timeLeft.value) }
    return remember { CountdownHolder(timeLeft, formatRes) }
}

/**
 * A hook for managing countdown functionality.
 *
 * This hook provides a way to create and manage countdown timers with various options
 * such as setting a target date or remaining time, custom intervals, and end callbacks.
 *
 * @param optionsOf A lambda to configure the countdown options
 * @return A [CountdownHolder] containing the current time left and formatted result
 *
 * @example
 * ```kotlin
 * val countdown = useCountdown {
 *     leftTime = 60.seconds  // Set initial countdown time
 *     interval = 1.seconds   // Update every second
 *     onEnd = {             // Callback when countdown ends
 *         println("Countdown finished!")
 *     }
 * }
 *
 * // Access the countdown values
 * val timeLeft = countdown.timeLeft.value
 * val formatted = countdown.formatRes.value
 * ```
 */
@Composable
fun useCountdown(optionsOf: CountdownOptions.() -> Unit): CountdownHolder = useCountdown(remember { CountdownOptions.optionOf(optionsOf) })

/**
 * Calculates the remaining time until the target date.
 *
 * @param target The target date to calculate remaining time from
 * @return The remaining duration, or 0 seconds if target is null or in the past
 */
@Stable
private fun calcLeft(target: Instant?): Duration {
    if (target == null) return 0.seconds
    val left = target - currentTime
    return if (left < 0.seconds) 0.seconds else left
}

/**
 * Represents formatted countdown time components.
 *
 * @property days Number of days remaining
 * @property hours Number of hours remaining (0-23)
 * @property minutes Number of minutes remaining (0-59)
 * @property seconds Number of seconds remaining (0-59)
 * @property milliseconds Number of milliseconds remaining (0-999)
 */
@Stable
data class FormattedRes(
    val days: Int,
    val hours: Int,
    val minutes: Int,
    val seconds: Int,
    val milliseconds: Int,
)

/**
 * Parses a duration into formatted time components.
 *
 * @param leftTime The duration to parse
 * @return A [FormattedRes] containing the parsed time components
 */
@Stable
private fun parseDuration(leftTime: Duration): FormattedRes = FormattedRes(
    days = (leftTime.inWholeDays).toInt(),
    hours = ((leftTime.inWholeHours) % 24).toInt(),
    minutes = ((leftTime.inWholeMinutes) % 60).toInt(),
    seconds = ((leftTime.inWholeSeconds) % 60).toInt(),
    milliseconds = (leftTime.inWholeMilliseconds % 1000).toInt()
)

/**
 * Holder class for countdown state and formatted results.
 *
 * @property timeLeft The current remaining time as a [State]
 * @property formatRes The formatted time components as a [State]
 */
@Stable
data class CountdownHolder(
    val timeLeft: State<Duration>,
    val formatRes: State<FormattedRes>,
)
